package com.eastmoney.emis.edda.application.service;

import com.eastmoney.emis.edda.application.aop.Lock;
import com.eastmoney.emis.edda.application.vo.EddaBindVo;
import com.eastmoney.emis.edda.common.Constants;
import com.eastmoney.emis.edda.config.HsbcEddaConfig;
import com.eastmoney.emis.edda.domain.entity.EddaApply;
import com.eastmoney.emis.edda.domain.entity.EddaBind;
import com.eastmoney.emis.edda.domain.entity.EddiInstruction;
import com.eastmoney.emis.edda.domain.event.EddaAuthEvent;
import com.eastmoney.emis.edda.domain.event.EddiInstructionEvent;
import com.eastmoney.emis.edda.domain.event.EventPublisher;
import com.eastmoney.emis.edda.domain.repository.HsbcEddaRepository;
import com.eastmoney.emis.edda.domain.repository.HsbcEddiRepository;
import com.eastmoney.emis.edda.domain.service.IdService;
import com.eastmoney.emis.edda.domain.service.edda.EddaBindAndDirectDebitCommand;
import com.eastmoney.emis.edda.domain.service.edda.EddaBindCommand;
import com.eastmoney.emis.edda.domain.valueobject.EddaApplyStatus;
import com.eastmoney.emis.edda.domain.valueobject.EddiOperation;
import com.eastmoney.emis.edda.domain.valueobject.TimeFormatter;
import com.eastmoney.emis.edda.infrastructure.edda.service.HsbcApiErrorResolver;
import com.eastmoney.emis.edda.infrastructure.exception.EddaException;
import com.eastmoney.emis.edda.infrastructure.exception.EddaExceptionStatus;
import com.eastmoney.emis.edda.infrastructure.hsbc.HsbcEddaApi;
import com.eastmoney.emis.edda.infrastructure.hsbc.dto.AuthReq;
import com.eastmoney.emis.edda.infrastructure.hsbc.dto.AuthResp;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.time.LocalDate;

/**
 * @description: Description
 * @projectName: EDDA-HSBC
 * @package: com.eastmoney.emis.edda.domain.service
 * @author: 汪程程
 * @date: 2023/3/28 1:35 PM
 * @version: 1.0
 */

@Service
public class EddaBindAndDirectDebitServiceImpl implements EddaBindAndDirectDebitService {

    @Autowired
    private HsbcEddaConfig config;
    @Autowired
    private HsbcEddaRepository eddaRepository;
    @Autowired
    private HsbcEddiRepository eddiRepository;
    @Autowired
    private IdService idService;
    @Autowired
    private HsbcEddaApi hsbcEddaApi;
    @Autowired
    private HsbcApiErrorResolver errorResolver;
    @Autowired
    private EventPublisher eventPublisher;

    @Override
    @Lock
    public EddaBindVo bindAndDirectDebit(EddaBindAndDirectDebitCommand command) {
        EddaApply apply = eddaRepository.findEddaApplyInProgress(command.getClientId(), command.getBankCode(), command.getBankAccount());
        if (apply != null) {
            return EddaBindVo.EddaBindVoBuilder.create()
                    .withRegisterId(apply.getRegisterId())
                    .withOtpId(null)
                    .build();
        }

        // edda bind
        EddaBind eddaBind = createOrRebind(command);
        // init edda apply
        AuthReq authReq = new AuthReq(command, config.getCreditorAccount());
        authReq.MerchantRequestIdentification = idService.generateId();
        AuthResp authResp = hsbcEddaApi.authorization(authReq);
        // request hsbc api
        EddaApply eddaApply = buildEddaApply(authReq, authResp, command);
        eddaRepository.save(eddaApply);
        publishAuthEvent(eddaApply, authResp);
        // init eddi
        EddiInstruction eddiInstruction = new EddiInstruction(eddaBind);
        eddiInstruction.setApplySerialNo(idService.generateId());
        eddiInstruction.setOccurBalance(new BigDecimal(command.getInstructedAmount()));
        eddiInstruction.setNote(Constants.EDDA_NOTE_PREFIX + eddaApply.getEid());
        eddiRepository.saveDebitInstruction(eddiInstruction);
        publishEddiEvent(eddiInstruction, "绑定并入金", null);

        boolean isHsbc = Constants.HSBC_BANK_CODE.equals(eddaApply.getBankCode());
        boolean requestHsbcApiFail = !authResp.success();
        // 汇丰由于OTP的存在 直接失败
        if (isHsbc && requestHsbcApiFail) {
            eddaApply.status(EddaApplyStatus.FAIL);
            eddaBind.fail(eddaApply);
            eddaRepository.save(eddaBind);
            publishAuthEvent(eddaApply, null);
        }
        eddaApply.setNote(Constants.EDDI_NOTE_PREFIX + eddiInstruction.getEid());
        eddaRepository.save(eddaApply);

        errorResolver.resolveAndTrow(authResp);
        return EddaBindVo.EddaBindVoBuilder.create()
                .withRegisterId(eddaApply.getRegisterId())
                .withOtpId(authResp.OtpIdentificationNumber)
                .build();
    }

    private EddaBind createOrRebind(EddaBindAndDirectDebitCommand command) {
        EddaBind eddaBind = eddaRepository.findEddaBindByClientIdAndBankAccount(
                command.getClientId(), command.getBankCode(), command.getBankAccount());
        if (eddaBind != null && eddaBind.finished()) {
            throw new EddaException(EddaExceptionStatus.DUPLICATE_BIND);
        }
        if (eddaBind == null) {
            eddaBind = new EddaBind(command);
        } else {
            eddaBind.merge(command);
            eddaBind.rebind(command.isHsbc());
        }
        eddaRepository.save(eddaBind);
        return eddaBind;
    }

    private EddaApply buildEddaApply(AuthReq req, AuthResp authResp, EddaBindCommand command) {
        EddaApply eddaApply = new EddaApply();
        eddaApply.status(command.isHsbc() ? EddaApplyStatus.OTP_UNCONFIRMED : EddaApplyStatus.PROCESSING);
        eddaApply.setClientId(command.getClientId());
        eddaApply.setBankCode(command.getBankCode());
        eddaApply.setBankAccount(command.getBankAccount());
        eddaApply.setCustomEngName(command.getCustomEngName());
        eddaApply.setRegisterId(req.MerchantRequestIdentification);
        eddaApply.setCreditReference(req.CreditorReference);
        eddaApply.setMobile(req.DebtorMobileNumber);
        eddaApply.setDebtorId(req.DebtorPrivateIdentification);
        eddaApply.setDebtorSchemeName(req.DebtorPrivateIdentificationSchemeName);
        eddaApply.setCurrency(req.MaximumAmountCurrency);
        eddaApply.setMaximumAmount(req.MaximumAmount);
        eddaApply.setFrequencyType(req.Occurrences.FrequencyType);
        eddaApply.setLanguage(req.SmsLanguageCode);
        eddaApply.setMandateId(authResp.MandateIdentification);
        eddaApply.setGrantExpiryDate(LocalDate.parse(req.Occurrences.DurationToDate, TimeFormatter.YYYY_MM_DD));
        return eddaApply;
    }

    private void publishAuthEvent(EddaApply eddaApply, AuthResp authResp) {
        EddaAuthEvent event = new EddaAuthEvent(eddaApply.getClientId());
        event.setRefEid(eddaApply.getEid());
        event.setStatus(eddaApply.getStatus());
        if (authResp != null) {
            event.setResult(authResp.result());
            event.setNote(authResp.contextString());
        }
        eventPublisher.publish(event);
    }

    private void publishEddiEvent(EddiInstruction eddiInstruction, String req, String error) {
        EddiInstructionEvent event = new EddiInstructionEvent(eddiInstruction);
        event.setOperation(EddiOperation.APPLY.getValue());
        event.setApplyReq(req);
        event.setErrorReason(error);
        eventPublisher.publish(event);
    }

}
